Fix global PM probes spawning bare names from the project (#421, #434, #438, #440) - #442
Conversation
Global mode asked npm, yarn, pnpm, bun, gem and composer where their global installs live by spawning the bare tool name. On Windows those tools are .cmd/.bat shims that a bare spawn never finds, so scan -g silently reported nothing to patch. The yarn probe also ran inside the scanned project, where Yarn Berry runs the project's own "global" script and its output picked the directory scanned as global. Probes now resolve the tool through PATHEXT (and never from a relative PATH entry), npm-family global probes run from the home directory, and Composer's home falls back to %APPDATA%\Composer and $XDG_CONFIG_HOME/composer like Composer does. Fixes #421, #434, #438, #440. Assisted-by: Claude Code:claude-opus-5-5
A Windows App Execution Alias (the Store python3.exe) is a reparse point the PATH lookup can't stat, though a bare spawn launches it. The Python probe shares this runner, so fall back to the bare name on Windows when the lookup finds nothing, rather than lose an interpreter that used to be found. Also satisfies clippy's redundant closure lint. Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
|
[agent] Two red checks on
If either fails again on re-run, I'll treat it as real and root-cause it. Generated by Claude Code |
Global Composer discovery now also tries Composer's own defaults, %APPDATA%\Composer and $XDG_CONFIG_HOME/composer. On a Windows runner APPDATA points at a real Composer home, which outranks the ~/.composer and ~/.config/composer candidates these tests stage. Unset both variables there, as the tests already do for HOME and PATH, so they keep exercising the HOME candidates. Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
An earlier cargo fmt run over the whole workspace reformatted 125 files this change doesn't touch. Restore them to main so the diff holds only the global-probe fix. Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
|
[burn-down agent] Ready for review on
Note: Slack announcement could not be sent this run (no Slack send tool available in the agent session); next run will retry. Generated by Claude Code |
Union the CHANGELOG Fixed entries from both sides. Co-Authored-By: Claude <noreply@anthropic.com>
|
bugbot run Generated by Claude Code |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KKwBoKTsqpGR3ZcCAcEyCA
The no-composer and empty-HOME tests still inherited APPDATA and XDG_CONFIG_HOME. Global discovery now probes %APPDATA%\Composer and $XDG_CONFIG_HOME/composer, so a machine with a real Composer home there made both tests see a vendor dir and fail. Unset both variables in these tests too, as the sibling HOME-fallback tests already do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KKwBoKTsqpGR3ZcCAcEyCA
|
bugbot run Generated by Claude Code |
|
Generated by Claude Code |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KKwBoKTsqpGR3ZcCAcEyCA
|
bugbot run Generated by Claude Code |
|
Burn-down agent check: ready for review at
Generated by Claude Code |
# Conflicts: # CHANGELOG.md
|
bugbot run Generated by Claude Code |
|
Burn-down agent: ready for review again at
Generated by Claude Code |
|
Reviewed [P1] Preserve absolute-PATH filtering in the Windows fallback — crates/socket-patch-core/src/utils/process.rs:207. When Consequently, on Windows with Validation: |
When the safe lookup found no tool, the Windows fallback for App Execution Aliases handed the bare name back to std. Its Windows search also walks relative PATH entries such as '.', resolved against the parent's cwd before the child's current_dir applies, so with '.' on PATH an npm.exe or yarn.exe planted in the scanned project ran during the global probe despite the neutral child cwd. Look for the alias as <name>.exe on absolute PATH entries only, using symlink_metadata since the reparse point can't be followed, and never spawn the bare name. A Windows e2e test plants a copy of cmd.exe as npm.exe in the project with only '.' on PATH and checks the global npm probe finds nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KKwBoKTsqpGR3ZcCAcEyCA
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KKwBoKTsqpGR3ZcCAcEyCA
|
Thanks, the [P1] is right. The old comment saying
I couldn't run or type-check the Windows test here: there's no Windows host, and cross-compiling Generated by Claude Code |
|
I don't think this PR caused it:
No fix exists for it yet. I'll re-run the failed job once this CI run finishes (GitHub refuses a re-run while the run is still going). If it fails again, I'll treat it as a real nondeterminism in the crawler-vs-oracle walk and look into it. Generated by Claude Code |
|
Follow-up reviewed The Windows alias fallback now considers only absolute PATH entries and passes the selected absolute Validation: The final main-merge commit leaves the process implementation and probe tests identical to tested |
|
bugbot run Generated by Claude Code |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit dce7d1a. Configure here.
|
[burn-down agent] Re-verified Ready for review at
Slack announcement not sent: the Slack connector in this session has no send-message tool. Generated by Claude Code |
|
Re-reviewed The Ubuntu test job now passes on this same head, as do the Windows and macOS test jobs. The Windows absolute-PATH security fix remains unchanged, and the branch merges cleanly with current Fresh local validation: 4 oracle tests, 15 process tests, and 6 global-probe integration tests pass on macOS, including the oracle test that previously failed in Ubuntu CI. The old failure cleared on a same-SHA CI rerun; the local replay also passed. |
LLM Description written by Claude Code:claude-opus-5-5
Fixes #421
Fixes #434
Fixes #438
Fixes #440
Summary
On Windows,
scan -g,get -gandvex -gnow find globally installed npm, yarn, pnpm, bun, RubyGems and Composer packages. Before this change they reported an empty, successful scan. The npm-family global lookups also no longer run inside the scanned project, so a Yarn Berry project's"global"script can't run or choose the directory that gets scanned (and patched) as the global install. Composer's global home also falls back to Composer's own platform defaults.Priority note: the cluster is p1 (npm/yarn/RubyGems) and includes one p2 member (#438, Composer), because they share the same boundary.
Root cause
Global discovery asks each package manager where its global tree lives:
npm root -g,yarn global dir,pnpm root -g,bun pm bin -g,gem env gemdir|gempathandcomposer global config home. Every one of these probes went throughSystemCommandRunner::run(crates/socket-patch-core/src/utils/process.rs), which calledCommand::new(bin)on the bare tool name. That caused two problems:stdonly tries<name>.exe. These tools install asnpm.cmd,yarn.cmd,gem.cmdandcomposer.bat, so every probe returnedNoneand discovery came back silently empty (On Windows (RubyInstaller),scan -g/get -g/vex -gfind no global gems becausegem envis spawned as baregem, which never resolves togem.cmd#421, On Windows,scan -g/get -g/vex -gfind no global npm packages becausenpm root -gis spawned as barenpm, which never resolves tonpm.cmd#434, On Windows, scan -g finds no Composer global packages in the default %APPDATA%\Composer home, so apply -g and vex -g silently do nothing #438).globalcommand and dispatchesyarn global dirto the project's"global"script, whose stdout then picked the "global" directory (scan -ginside a Yarn Berry project runs the project'sglobalpackage.json script and scans whatever directory it prints as a global install #440). A fix for the.cmdresolution alone would have madescan -ginside a Yarn Berry project runs the project'sglobalpackage.json script and scans whatever directory it prints as a global install #440 reproduce on Windows too, which is why these ship together.Fix
SystemCommandRunnerresolves the program with the existingresolve_tool(PATHEXT on Windows, absolute PATH entries only, so a tool planted in the project via.on PATH is never run) and spawns the resolved path throughcommand_for. It never spawns the bare name.resolve_toolfinds nothing,resolve_app_alias_withlooks for<name>.exeon absolute PATH entries only. It usessymlink_metadata, because an App Execution Alias such as the Storepython3.exeis a reparse point thatis_filecan't follow. An earlier revision fell back tostd's own search here, which walks relative PATH entries such as.against the parent's cwd, so it could run an executable planted in the project. That fallback is gone.GlobalProbeRunnerruns the probe from a neutral directory: the user's home (absolute only), otherwise the drive root, and never the project. The npm, yarn, pnpm and bun global probes andcomposer global config homeuse it.gem envkeeps the project cwd on purpose, because rbenv and chruby choose the Ruby from the project's.ruby-version, and local mode uses the same lookup.get_composer_homenow probes Composer's own defaults:%APPDATA%\Composerfirst on Windows, and~/.composer, then$XDG_CONFIG_HOME/composer, then~/.config/composerelsewhere. Relative or empty variables are ignored.APPDATAandXDG_CONFIG_HOME, as they already do forHOMEandPATH, so a real Composer home on the test machine can't change their result.Tests
The new suite
crates/socket-patch-core/tests/global_probe_spawn_e2e.rsputs fake tools on PATH the way they install on each OS: an executableshscript on Unix, and a<name>.cmdshim with no.exeon Windows.npm_family_global_probes_find_the_installed_shims(npm/pnpm/bun)test (windows-latest)yarn_global_probe_runs_outside_the_scanned_projectleft: "/tmp/…/proj",right: "/tmp/…/home". On Windows it also fails, because the shim isn't found. Passes on windows-latestglobal_probe_ignores_a_tool_planted_on_a_relative_path_entry(Unix)left: Ok("/planted/node_modules")global_probe_never_runs_an_executable_planted_in_the_project(Windows)cmd.exeplanted asnpm.exein the project with only.on PATH. The old fallback would run it and return its banner as the prefix. Not executed locally (no Windows host); first run is on windows-latestglobal_gem_paths_come_from_the_installed_gem_shimgem.cmd). Passes on windows-latestcomposer_home_comes_from_the_installed_composer_shimcomposer.cmd). Passes on windows-latestcomposer_home_falls_back_to_xdg_config_home(Unix),composer_home_falls_back_to_appdata_on_windows(Windows)left: []. The APPDATA test passes on windows-latestThere are also unit tests:
neutral_probe_dir_prefers_an_absolute_home_and_never_the_cwd, andresolve_app_alias_skips_relative_entries(ayarn.exereachable only through., an empty entry or a bare dir name is never chosen; one on an absolute entry is).Commands run locally (Linux) on
dce7d1a:cargo clippy --workspace --all-features -- -D warnings: cleancargo test -p socket-patch-core --lib utils::process: 15 passcargo test -p socket-patch-core --test global_probe_spawn_e2e --test crawler_composer_e2e --test crawler_npm_e2e: 6, 33 and 82 passcargo test --workspace --all-features --no-fail-fast(earlier revision): everything passes except 12 permission-injection tests that fail only because this sandbox runs as uid 0. None touch the probe code; CI runs as non-root.ringneeds a MinGW toolchain this sandbox lacks.test (windows-latest)is its first build.mainisn't rustfmt-clean under the pinned 1.93.1 toolchain, and CI doesn't gate on it, so I didn't apply a workspace-widecargo fmt.npm/,pypi/andgem/only dispatch the binary.Follow-ups (not in this PR)
yarn global dirdoesn't exist before yarn 1.1.0, and there's no fallback #437: yarn 1.0.x has noyarn global dir, so it needs a default-folder fallback.vendor-dircascade inside the global home.scan -g/get -g/vex -gfind no global gems becausegem envis spawned as baregem, which never resolves togem.cmd#421 notes that the hard-coded gem fallback list lacks RubyInstaller and XDG dirs. Withgem envworking on Windows, that list is no longer the discovery path there, so it's left as is.🤖 Generated with Claude Code
https://claude.ai/code/session_01KKwBoKTsqpGR3ZcCAcEyCA
Note
Medium Risk
Changes how external package-manager CLIs are resolved and spawned for global discovery; mistakes could mis-target global install trees or skip valid tools, but scope is limited to probe/discovery paths with extensive regression tests.
Overview
Global mode (
-g) now discovers machine-wide npm, yarn, pnpm, bun, RubyGems, and Composer installs reliably instead of often returning an empty scan.Process spawning resolves package-manager binaries via
resolve_tool(PATHEXT on Windows for.cmd/.batshims) and spawns the resolved path; on Windows,resolve_app_alias_withcan pick up App Execution Aliases on absolutePATHentries only, without falling back to bare names that could run project-local executables.Global prefix probes for npm, yarn, pnpm, bun, and
composer global config homeuse a newGlobalProbeRunnerthat runs from a neutral directory (absolute home, else drive root), so Yarn Berry cannot answeryarn global dirvia a project"global"script and relativePATHentries cannot hijack the probe.Composer global home fallbacks now follow Composer’s own defaults:
%APPDATA%\Composeron Windows and XDG/~/.config/composerelsewhere, via extractedcomposer_home_candidates.Changelog and e2e/unit tests cover shims, neutral cwd, PATH hardening, and Composer fallback ordering.
Reviewed by Cursor Bugbot for commit dce7d1a. Configure here.
Generated by Claude Code